Lektion 22



Diese Lektion wurde von Jens Schneider geschrieben. Sie basiert etwas auf Lektion 6, obwohl viele Änderungen gemacht wurden. In dieser Lektion werden Sie lernen:

Da mindestens drei der vier genannten Punkte als "fortgeschrittene Rendering Techniken" angesehen werden können, sollten Sie zumindestens ein grundlegendes Verständnis der OpenGL Rendering Pipeline haben. Sie sollten die meisten Befehle, die in diesem Tutorial verwendet werden, bereits kennen und Sie sollten mit Vektor-Mathematik vertraut seien. Ab und zu werden Sie Absätze entdecken, die mit Anfang Theorie (...) als Überschrift haben und mit Ende Theorie (...) enden. Diese Abschnitte versuchen Ihnen die Theorie hinter dem Thema, welches in den Klammern angegeben ist, näher zu bringen. Damit können Sie diese Abschnitte leicht überspringen, wenn Sie mit dem Thema bereits vertraut sind. Wenn Probleme auftauchen, den Code zu verstehen, sollten Sie zurück zu den Theorie-Abschnitten gehen und dort nochmal nachlesen.

Zu guter Letzt: Diese Lektion besteht aus mehr als 1200 Codezeilen, wobei große Teile nicht nur langweilig sind, sondern auch aus vorherigen Tutorials bekannt sind. Deshalb werde ich nicht jede einzelne Zeile kommentieren, sondern nur die interessanten. Wenn Sie etwas wie >…< entdecken, bedeutet das, dass Codezeilen ausgelassen wurden.

Fangen wir an:

#include < windows.h>								// Header Datei für Windows
#include < stdio.h>								// Header Datei für Standard Input/Output
#include < gl\gl.h>								// Header Datei für die OpenGL32 Library
#include < gl\glu.h>								// Header Datei für die GLu32 Library
#include < gl\glaux.h>								// Header Datei für die GLaux Library
#include "glext.h"								// Header Datei für Multitexturing
#include < string.h>								// Header Datei für die String Library
#include < math.h>								// Header Datei für die Math Library

Das GLfloat MAX_EMBOSS spezifiziert die "Stärke" des Bump Mapping-Effektes. Größere Werte verstärken den Effekt, aber reduzieren gleichzeitig die optische Qualität, da an den Ecken der Oberfläche sogenannte "Artefakte" auftreten.

#define MAX_EMBOSS (GLfloat)0.01f						// Maximum Emboss-Translate. Inkrementiere um höhere "Immersion" zu erhalten

Ok, nun bereiten wir uns darauf vor, die GL_ARB_multitexture Extension zu verwenden. Das ist ziemlich einfach:

Die meisten Beschleuniger haben mehr als nur eine Textur-Einheit heutzutage. Um dieses Feature ausnutzen zu können, müssen Sie auf GL_ARB_multitexture-Support überprüfen, was Ihnen erlaubt zwei oder mehr verschiedene Texturen auf ein OpenGL-Primitv zu mappen, in nur einem Durchgang. Klingt nicht allzu mächtig, ist es aber. Fast immer wenn Sie etwas programmieren, erhöht es die visuelle Qualität wenn Sie eine Textur auf das Objekt mappen. Da Sie normalerweise mehrere "Durchläufe" (passes) benötigen, bestehend aus überlagernden Textur-Auswahl und Zeichnen der Geometrie, kann das alles recht schnell rechenintensiv werden. Aber kein Grund zur Sorge, das klären wir später noch.

Nun zurück zum Code: __ARB_ENABLE wird dazu verwendet Multitexturing für einen speziellen Compiler-Durchlauf komplett zu überschreiben. Wenn Sie Ihre OpenGL Extensionen sehen möchten, kommentieren Sie das #define EXT_INFO einfach ein. Als nächstes überprüfen wir unsere Extensionen während der Laufzeit um sicher zu gehen, dass unser Code portabel bleibt. Deshalb benötigen wir etwas Platz für ein paar Strings. Das sind die folgenden zwei Zeilen. Nun wollen wir unterscheiden, ob wir fähig sind, Multitexturing zu verwenden und ob wir es auch wirklich verwenden, weshalb wir zwei weitere Flags benötigen. Zu letzt müssen wir wissen, wieviele Textur-Einheiten es gibt (auch wenn wir nur zwei davon verwenden werden). Mindestens eine Textur-Einheit ist auf jedem OpenGL-fähigen Beschleuniger vorhanden, weshalb wir maxTexelUnits mit 1 initialisieren.

#define __ARB_ENABLE true							// wird verwendet um ARB Extensionen komplett zu deaktivieren
// #define EXT_INFO								// kommentieren Sie diese Zeile ein, um alle Ihre Extensionen zu sehen
#define MAX_EXTENSION_SPACE 10240						// Zeichen für Extension-Strings
#define MAX_EXTENSION_LENGTH 256						// Maximale Zeichen in einem Extension-String
bool multitextureSupported=false;						// Flag ob Multitexturing unterstützt wird oder nicht
bool useMultitexture=true;							// wird es verwendet, wenn es unterstützt wird?
GLint maxTexelUnits=1;								// Anzahl der Texel-Pipelines. Hier gibt es mindestens eine.

Die folgenden Zeilen werden benötigt um die Extensionen auf die C++ Funktions-Aufrufe zu "linken". Behandeln Sie die PFN-was-auch-immer als vordefinierte Datentypen, die fähig sind Funktions-Aufrufe zu beschreiben. Da wir noch unsicher sind, ob wir die Funktionen für diese Prototypen haben werden, setzen wir sie auf NULL. Der Befehl glMultTexCoordifARB mappt auf das allseits bekannte glTexCoordif, welches eine i-dimensionale Textur-Koordinate spezifiziert. Beachten Sie, dass dies die glTexCoordif-Befehle komplett ersetzen kann. Da wir nur die GLfloat-Version verwenden, benötigen wir lediglich Prototypen für die Befehle, die mit einem "f" enden. Andere sind ebenfalls verfügbar ("fv", "i", etc.). Die letzten beiden Prototypen sind auf die aktive Textur-Einheit zu setzen, die zur Zeit die Textur-Bindungen erhält ( glActiveTextureARB() ) und zur Bestimmung welche Textur-Einheit mit dem ArrayPointer-Befehl (a.k.a Client-Subset, deshalb glClientActiveTextureARB) verbunden ist. Übrigens: ARB ist eine Abkürzung für "Architectural Review Board". Extensionen mit einem ARB in ihrem Namen werden nicht unbedingt bei einer OpenGL-konformen Implementation benötigt, aber es wird angenommen, dass sie weitverbreitet unterstützt wird. Zur Zeit hat es nur die Multitexture-Extension es zum ARB-Status gebracht. Das mag man als Zeichen für den unglaublichen Einfluss bezüglich der Geschwindigkeit deuten, die Multitexturing bei verschiedenen fortgeschrittenen Rendering-Techniken hat.

Die ausgelassenen Zeilen sind GDI-Context, Handles, etc.

PFNGLMULTITEXCOORD1FARBPROC	glMultiTexCoord1fARB	= NULL;
PFNGLMULTITEXCOORD2FARBPROC	glMultiTexCoord2fARB	= NULL;
PFNGLMULTITEXCOORD3FARBPROC	glMultiTexCoord3fARB	= NULL;
PFNGLMULTITEXCOORD4FARBPROC	glMultiTexCoord4fARB	= NULL;
PFNGLACTIVETEXTUREARBPROC	glActiveTextureARB	= NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC	glClientActiveTextureARB= NULL;

Wir benötigen ein paar globale Variablen:

GLuint  filter=1;								// welcher Filter verwendet wird
GLuint  texture[3];								// Speicherplatz für 3 Texturen
GLuint  bump[3];								// Unsere Bumpmappings
GLuint  invbump[3];								// Inverse Bumpmaps
GLuint  glLogo;									// Handle für das OpenGL-Logo
GLuint  multiLogo;								// Handle für das Multitexture-Enabled-Logo
GLfloat LightAmbient[]	= { 0.2f, 0.2f, 0.2f};					// Ambientes Licht ist 20% Weiss
GLfloat LightDiffuse[]	= { 1.0f, 1.0f, 1.0f};					// Diffuses Licht ist Weiss
GLfloat LightPosition[]	= { 0.0f, 0.0f, 2.0f};					// Position ist irgendwo vor dem Screen
GLfloat Gray[]		= { 0.5f, 0.5f, 0.5f, 1.0f};

Der nächste Codeblock enthält die nummerische Repräsentation eines texturierten Würfels, der aus GL_QUADS erzeugt wird. Jede fünf Nummern spezifizieren einen Satz 2D-Textur-Koordinaten und einen Satz 3D-Vertex-Koordinaten. Damit können wir den Würfel mittels for-Schleifen erzeugen, da wir den Würfel mehrmals benötigen. Dem data-Block folgt der bereits bekannte WndProc()-Prototyp aus vorangegangenen Lektionen.

// Data enthält die Seiten des Würfels im Format 2xTexCoord, 3xVertex.
// Beachten Sie, dass die Tesselation des Würfels nur das absolute Minimum ist.

GLfloat data[]= {
	// VORDERE SEITE
	0.0f, 0.0f,		-1.0f, -1.0f, +1.0f,
	1.0f, 0.0f,		+1.0f, -1.0f, +1.0f,
	1.0f, 1.0f,		+1.0f, +1.0f, +1.0f,
	0.0f, 1.0f,		-1.0f, +1.0f, +1.0f,
	// HINTERE SEITE
	1.0f, 0.0f,		-1.0f, -1.0f, -1.0f,
	1.0f, 1.0f,		-1.0f, +1.0f, -1.0f,
	0.0f, 1.0f,		+1.0f, +1.0f, -1.0f,
	0.0f, 0.0f,		+1.0f, -1.0f, -1.0f,
	// obere Seite
	0.0f, 1.0f,		-1.0f, +1.0f, -1.0f,
	0.0f, 0.0f,		-1.0f, +1.0f, +1.0f,
	1.0f, 0.0f,		+1.0f, +1.0f, +1.0f,
	1.0f, 1.0f,		+1.0f, +1.0f, -1.0f,
	// untere Seite
	1.0f, 1.0f,		-1.0f, -1.0f, -1.0f,
	0.0f, 1.0f,		+1.0f, -1.0f, -1.0f,
	0.0f, 0.0f,		+1.0f, -1.0f, +1.0f,
	1.0f, 0.0f,		-1.0f, -1.0f, +1.0f,
	// rechte Seite
	1.0f, 0.0f,		+1.0f, -1.0f, -1.0f,
	1.0f, 1.0f,		+1.0f, +1.0f, -1.0f,
	0.0f, 1.0f,		+1.0f, +1.0f, +1.0f,
	0.0f, 0.0f,		+1.0f, -1.0f, +1.0f,
	// linke Seite
	0.0f, 0.0f,		-1.0f, -1.0f, -1.0f,
	1.0f, 0.0f,		-1.0f, -1.0f, +1.0f,
	1.0f, 1.0f,		-1.0f, +1.0f, +1.0f,
	0.0f, 1.0f,		-1.0f, +1.0f, -1.0f
};

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);				// Deklaration für WndProc

Der nächste Codeblock bestimmt die unterstützten Extensionen zur Laufzeit.

Zuerst können wir annehmen, dass wir einen langen String haben werden, der alle unterstützten Extensionen als '\n' getrennte Substrings enthalten wird. Alles was wir also machen müssen, ist nach einem '\n' suchen und anfangen den String mit search zu vergleichen, bis wir auf ein weiteres '\n' treffen oder bis der String nicht mehr mit search übereinstimmt. Im ersten Fall gebe true für "gefunden" zurück, im anderen Fall, nehme den nächsten Substring, bis Sie das Ende des Strings erreicht haben. Sie müssen am Anfang des Strings etwas aufpassen, da er nicht mit einem Newline-Zeichen beginnt.

Übrigens: eine gängige Regel ist es, IMMER während der Laufzeit auf die Verfügbarkeit von Extensionen zu überprüfen!

bool isInString(char *string, const char *search) {
	int pos=0;
	int maxpos=strlen(search)-1;
	int len=strlen(string);
	char *other;
	for (int i=0; i< len; i++) {
		if ((i==0) || ((i>1) && string[i-1]=='\n')) {			// Neue Extension beginnt hier!
			other=&string[i];
			pos=0;							// Beginne neue Suche
			while (string[i]!='\n') {				// durchsuche gesamten Extension-String
				if (string[i]==search[pos]) pos++;		// nächste Position
				if ((pos>maxpos) && string[i+1]=='\n') return true;	// wir haben eine Gewinner!
				i++;
			}
		}
	}
	return false;								// Sorry, nichts gefunden!
}

Nun müssen wir den Extension-String hohlen und ihn in eine durch '\n' getrennte Reihenfolge bringen, damit wir nach unserer gewünschten Extension suchen können. Wenn wir den Substring "GL_ARB_MULTITEXTURE" darin finden, wird dieses Feature unterstützt. Aber wir können es nur verwenden, wenn __ARB_ENABLE ebenfalls true ist. Als letztes muss noch GL_EXT_texture_env_combine unterstützt werden. Diese Extension führt neue Arten ein, wie die Textur-Einheiten zusammenarbeiten. Wir benötigen dies, da GL_ARB_multitexture die Ausgabe nur aus einer Textur-Einheit zur nächsten mit der nächst höheren Zahl füttert. Deshalb überprüfen wir auf diese Extension, anstatt eine weitere komplexe Blending-Gleichung zu verwenden (die nicht ganz den selben Effekt erzielen würde!) Wenn alle Extensionen unterstützt werden und wir nicht überschrieben wurden, bestimmen wir als erstes wieviele Textureinheiten verfügbar sind und speichern die Anzahl in maxTexelUnits. Dann müssen wir die Funktionen mit unseren Namen linken. Das wird mit den wglGetProcAdress()-Aufrufen gemacht, wobei als Parameter ein String mit dem Namen des Funktionsaufrufes und einem Prototyp-Cast übergeben wird, um sicherzustellen, dass wir den richtigen Funktionstypen bekommen.

bool initMultitexture(void) {
	char *extensions;
	extensions=strdup((char *) glGetString(GL_EXTENSIONS));			// hole Extension String
	int len=strlen(extensions);
	for (int i=0; i< len; i++)						// trenne ihn mit Newline anstatt von Leerzeichen
		if (extensions[i]==' ') extensions[i]='\n';

#ifdef EXT_INFO
	MessageBox(hWnd,extensions,"supported GL extensions",MB_OK | MB_ICONINFORMATION);
#endif

	if (isInString(extensions,"GL_ARB_multitexture")			// wird Multitexturing unterstützt?
		&& __ARB_ENABLE							// überschreibe Flag
		&& isInString(extensions,"GL_EXT_texture_env_combine"))		// w5rd texture-environment-combining unterstützt?
	{      
		glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&maxTexelUnits);
		glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC) wglGetProcAddress("glMultiTexCoord1fARB");
		glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress("glMultiTexCoord2fARB");
		glMultiTexCoord3fARB = (PFNGLMULTITEXCOORD3FARBPROC) wglGetProcAddress("glMultiTexCoord3fARB");
		glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC) wglGetProcAddress("glMultiTexCoord4fARB");
		glActiveTextureARB   = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress("glActiveTextureARB");
		glClientActiveTextureARB= (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress("glClientActiveTextureARB");
              
#ifdef EXT_INFO
		MessageBox(hWnd,"The GL_ARB_multitexture extension will be used.","feature supported!",MB_OK | MB_ICONINFORMATION);
#endif

		return true;
	}
	useMultitexture=false;							// wir können es nicht verwenden, wenn es nicht unterstützt wird!
	return false;
}

InitLights() initialsiert einfach die OpenGL-Beleuchtung und wird später von InitGL() aufgerufen.

void initLights(void) {
        glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);				// Lade Light-Parameter nach GL_LIGHT1
        glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
        glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
        glEnable(GL_LIGHT1);
}

Hier laden wie VIELE Texturen. Da auxDIBImageLoad() einen eigene Fehlerbehandlung hat und LoadBMP() ohne try-catch-Block nicht gerade verlässlich war, habe ich es rausgeworfen. Aber nun zu unseren eigenen Lade-Routine. Als erstes laden wir die Basis-Textur und erzeugen daraus drei gefilterte Texturen ( GL_NEAREST, GL_LINEAR und GL_LINEAR_MIPMAP_NEAREST). Beachten Sie, dass ich nur eine Daten-Struktur habe, die die Bitmaps aufnimmt, da wir nur eine zur Zeit geöffnet haben müssen. Darüber hinaus habe ich eine weitere Daten-Struktur namens alpha hier eingefügt. Sie enthält die Alpha-Schichten der Texturen, so dass ich RGBA-Bilder als zwei Bitmaps speichern kann: ein 24bpp RGB und ein 8bpp grauskaliertes Alpha. Damit der Status-Indikator korrekt funktioniert, müssen wir den Bild-Block nach jedem Laden auf NULL resetten.

Beachten Sie auch, dass ich GL_RGB8 anstatt von nur "3" verwende, wenn ich die Textur-Art spezifiziere. Das ist konformanter zu anstehenden OpenGL-ICD Releases und sollte immer statt nur einer Zahl verwendet werden. Ich habe es in FETT für Sie markiert.

int LoadGLTextures() {								// Lade Bitmaps und konvertiere in Texturen
	bool status=true;							// Status Indikator
	AUX_RGBImageRec *Image=NULL;						// erzeuge Speicherplatz für die Textur
	char *alpha=NULL;

	// Lade das Tile-Bitmap für die Basis-Textur
	if (Image=auxDIBImageLoad("Data/Base.bmp")) {
		glGenTextures(3, texture);					// erzeuge drei Texturen

		// erzeuge Nearest-gefilterte Textur
		glBindTexture(GL_TEXTURE_2D, texture[0]);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image->data);

		// erzeuge Linear-gefilterte Textur
		glBindTexture(GL_TEXTURE_2D, texture[1]);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, Image->data);

		// erzeuge MipMapped Textur
		glBindTexture(GL_TEXTURE_2D, texture[2]);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
		gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, Image->sizeX, Image->sizeY, GL_RGB, GL_UNSIGNED_BYTE, Image->data);
	}
	else status=false;

	if (Image) {								// wenn Textur Existiert
		if (Image->data) delete Image->data;				// wenn Textur Bild existiert
		delete Image;
		Image=NULL;
	}

Nun laden wir die Bump Map. Für Gründe, die später erläutert werden, hat Sie nur eine Durchsichtigkeit von 50%, weshalb wir Sie auf die eine oder andere Weise skalieren müssen. Ich habe mich dazu entschlossen Sie mittels glPixelTrsferf()-Befehlen zu skalieren, welche spezifizieren, wie Daten aus Bitmaps in Texturen auf Pixel-Basis konvertiert werden. Ich verwende es um die RGB-Komponenten des Bitmaps auf 50% zu skalieren. Sie sollten wirklich sich mal die glPixelTransfer()-Befehlsfamilie ansehen, wenn Sie sie noch nicht in Ihren Programmen verwendet haben. Die sind wirklich nützlich.

Ein weiteres Thema ist, dass wir unser Bitmap nicht immer wieder und wieder in der Textur haben wollen. Wir wollen es nur einmal haben, gemapped auf die Textur-Koordinaten (s,t)=(0.0f, 0.0f) bis zu (s,t)=(1.0f, 1.0f). Alle anderen Textur-Koordinaten sollten auf einfaches schwarz gemapped werden. Das wird mit den zwei glTexParameteri()-Aufrufen erreicht, die ziemlich selbsterklärend sind und das Bitmap in s und t-Richtung "abschneiden".

	// Lade die Bumpmaps
	if (Image=auxDIBImageLoad("Data/Bump.bmp")) {
		glPixelTransferf(GL_RED_SCALE,0.5f);				// Skaliere RGB um 50%, so dass wir nur die 
		glPixelTransferf(GL_GREEN_SCALE,0.5f);				// halbe Intensität haben
		glPixelTransferf(GL_BLUE_SCALE,0.5f);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);	// Kein Wrapping, bitte!
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
		glGenTextures(3, bump);						// erzeuge drei Texturen

		// erzeuge Nearest-gefilterte Textur
		>…<

		// erzeuge Linear-gefilterte Textur
		>…<

		// erzeuge MipMapped Textur
		>…<

Sie kennen den Satz bereits: Aus Gründen, die später erläutert werden, müssen wir eine inverse Bump Map erzeugen, mit einer Durchsichtigkeit von fast 50 % wieder. Wie subtrahieren also die Bumpmap von purem Weiß, was als Integer-Zahlen gleich {255, 255, 255} ist. Da wir die RGB-Skalierung NICHT zurück auf 100% gesetzt haben (hat mich ungefähr drei Stunden gekostet, um herauszufinden, dass das einer der großen Fehler in meiner ersten Version war!), wird die inverse Bumpmap erneut auf 50% Durchsichtigkeit skaliert.

		for (int i=0; i<3*Image->sizeX*Image->sizeY; i++)		// Invertier die Bumpmap
			Image->data[i]=255-Image->data[i];

		glGenTextures(3, invbump);					// erzeuge drei Texturen

		// erzeuge Nearest-gefilterte Textur
		>…<

		// erzeuge Linear-gefilterte Textur
		>…<

		// erzeuge MipMapped Textur
		>…<
	}
	else status=false;
	if (Image) {								// wenn Textur existiert
		if (Image->data) delete Image->data;				// wenn Textur-Image existiert
		delete Image;
		Image=NULL;
	}

Das Laden der Logo-Bitmaps ist ziemlich einfach, außer die RGB-A re-Kombinierung, welche so klar sein sollte, dass Sie sie selbst verstehen. Beachten Sie, dass die Textur aus dem alpha-Speicherblock erzeugt wird, nicht aus dem Image-Speicherblock! Nur ein Filter wird hier verwendet.

	// Lade die Logo-Bitmaps
	if (Image=auxDIBImageLoad("Data/OpenGL_ALPHA.bmp")) {
		alpha=new char[4*Image->sizeX*Image->sizeY];
		// erzeuge Speicher für RGBA8-Textur
		for (int a=0; a< Image->sizeX*Image->sizeY; a++)
			alpha[4*a+3]=Image->data[a*3];				// nehme nur Rot-Werte als Alpha!
		if (!(Image=auxDIBImageLoad("Data/OpenGL.bmp"))) status=false;
		for (a=0; a< Image->sizeX*Image->sizeY; a++) {
			alpha[4*a]=Image->data[a*3];				// R
			alpha[4*a+1]=Image->data[a*3+1];			// G
			alpha[4*a+2]=Image->data[a*3+2];			// B
		}

		glGenTextures(1, &glLogo);					// erzeuge eine Texture

		// erzeuge Linear-gefilterte RGBA8-Textur
		glBindTexture(GL_TEXTURE_2D, glLogo);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Image->sizeX, Image->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, alpha);
		delete alpha;
	}
	else status=false;

	if (Image) {								// wenn Textur existiert
		if (Image->data) delete Image->data;				// wenn Textur-Image existiert
		delete Image;
		Image=NULL;
	}

	// Lade das "Extension Enabled"-Logo
	if (Image=auxDIBImageLoad("Data/multi_on_alpha.bmp")) {
		alpha=new char[4*Image->sizeX*Image->sizeY];			// erzeuge Speicher für RGBA8-Textur
		>…<
		glGenTextures(1, &multiLogo);					// erzeuge eine Textur
		// erzeuge Linear-gefilterte RGBA8-Textur
		>…<
		delete alpha;
	}
	else status=false;

	if (Image) {								// wenn Textur existiert
		if (Image->data) delete Image->data;				// wenn Textur-Image existiert
		delete Image;
		Image=NULL;
	}
	return status;								// gebe den Status zurück
}

Als nächstes kommt die einzige nicht geänderte Funktion ReSizeGLScene(). Ich habe sie hier weggelassen. Darauf folgt eine Funktion namens doCube(), die einen Würfel zeichnet, komplett mit normalisierten Normalenvektoren. Beachten Sie, dass diese Version nur Textur-Einheit #0 füttert, da glTexCoord2f(s,t) das Selbe wie glMultiTexCoord2f(GL_TEXTURE0_ARB,s,t) ist. Beachten Sie auch, dass man den Würfel mit verschachtelten Arrays machen könnte, aber das ist definitiv ein anderes Thema. Beachten Sie darüber hinaus, dass dieser Würfel NICHT mit einer Display-Liste erzeugt werden kann, da Display-Listen scheinbar interne Fließkommazahlen verwenden, deren Genauigkeit von GLfloat abweicht. Da dies zu verschiedenen unschönen Effekten führt, auch als "decaling"-Probleme bekannt, habe ich die Display-Listen weggelassen. Ich denke, dass eine allgemeine Regel für Multipass-Algorithmen es ist, die gesamte Geometrie entweder mit oder ohne Display Listen zu machen. Denken Sie also nicht mal dran, beides zu vermischen, selbst wenn es so scheint, dass es auf Ihrer Hardware läuft, kann es auf anderer Hardware ggf. gar nicht laufen!

GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
// Resize und initalisiere das GL Fenster
>…<

void doCube (void) {
	int i;
	glBegin(GL_QUADS);
		// vordere Seite
		glNormal3f( 0.0f, 0.0f, +1.0f);
		for (i=0; i<4; i++) {
			glTexCoord2f(data[5*i],data[5*i+1]);
			glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
		}
		// hintere Seite
		glNormal3f( 0.0f, 0.0f,-1.0f);
		for (i=4; i<8; i++) {
			glTexCoord2f(data[5*i],data[5*i+1]);
			glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
		}
		// obere Seite
		glNormal3f( 0.0f, 1.0f, 0.0f);
		for (i=8; i<12; i++) {
			glTexCoord2f(data[5*i],data[5*i+1]);
			glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
		}
		// untere Seite
		glNormal3f( 0.0f,-1.0f, 0.0f);
		for (i=12; i<16; i++) {
			glTexCoord2f(data[5*i],data[5*i+1]);
			glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
		}
		// rechte Seite
		glNormal3f( 1.0f, 0.0f, 0.0f);
		for (i=16; i<20; i++) {
			glTexCoord2f(data[5*i],data[5*i+1]);
			glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
		}
		// linke Seite
		glNormal3f(-1.0f, 0.0f, 0.0f);
		for (i=20; i<24; i++) {
			glTexCoord2f(data[5*i],data[5*i+1]);
			glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
		}
	glEnd();
}

Zeit um OpenGL zu initialisieren. Alles das Selbe wie in Lektion 06, außer dass ich initLights() hier aufrufe, anstatt sie zu setzen. Oh, und ich rufe natürlich das Multitexture-setup hier auf!

int InitGL(GLvoid)								// das gesamte Setup für OpenGL kommt hier hin
{
	multitextureSupported=initMultitexture();
	if (!LoadGLTextures()) return false;					// springe zur Textur Lade Routine
	glEnable(GL_TEXTURE_2D);						// aktiviere Textur Mapping
	glShadeModel(GL_SMOOTH);						// aktiviere Smooth Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);					// schwarzer Hintergrund
	glClearDepth(1.0f);							// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);						// aktiviere Depth Testing
	glDepthFunc(GL_LEQUAL);							// Die Art des Depth Testing
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);			// wirklich nette Perspektiven Berechnung

	initLights();								// initialsiere OpenGL Licht
	return true								// Initialisierung verlief OK
}
 

Nun kommt ungefähr 95% der Arbeit. Alle Verweise wie "Gründe dafür werden später genannt" werden im folgenden Theorie-Block gelöst.

Anfang Theorie ( Emboss Bump Mapping )

Wenn Sie einen Powerpoint-Viewer installiert haben, sollten Sie sich unbedingt die folgende Präsentation herunterladen:

Emboss Bump Mapping von Michael I. Gold, nVidia Corp. [.ppt, 309K]

Für die unter Ihnen, die keinen Powerpoint-Viewer haben, habe ich versucht die in dem Dokument enthaltenden Informationen ins html-Format zu konvertieren. Hier:

Emboss Bump Mapping

Michael I. Gold

NVidia Corporation

Bump Mapping

Richtiges Bump Mapping verwendet Per-Pixel Lighting (pro-Pixel-Beleuchtung). Emboss Bump Mapping

Emboss Bump Mapping ist ein Hack Diffuse Beleuchtung Berechnung

C=(L*N) x Dl x Dm Schätzung des diffusen Faktors L*N

Textur-Map repräsentiert ein Heightfield Schätzung Derivative

Embossing Approximates Derivative Berechne den Bump



1) Original Bump (H0).



2) Original Bump (H0) überlagert mit einem zweiten Bump (H1) etwas zur Lichtquelle hin verschoben.



3) Subtrahiere original Bump vom zweiten (H0-H1). Dadurch entstehen hellere (B) und dunklere (D) Flächen.

Berechne die Beleuchtung

Evaluiere Fragment Farbe Cf Ist das alles? Es ist so einfach!

Wir sind noch nicht ganz fertig. Wir müssen immer noch: Erzeugung einer Textur

Konserviere Texturen! Textur Offset Berechnung

Rotiere Lichtvektor in den normalen Raum Textur Offset Berechnung (Fortsetzung)

Verwende Normalen-Raum Licht Vektor für Offsets Implementation auf TNT

Berechne Vektoren, Texcoords auf dem Host Implementation auf TNT (Fortsetzung)

Combiner 0 Alpha-Setup: Ende Theorie ( Emboss Bump Mapping )

Obwohl wir es etwas anders machen, als die TNT-Implementation, damit unser Programm auf ALLEN Beschleunigern läuft, können wir hier zwei oder drei Dinge lernen. Eine Sache ist, dass Bump Mapping ein Multi-pass Algorithmus auf den meisten Karten ist (nicht auf der TNT-Familie, wo es als 2-Texturen-Durchlauf implementiert werden kann). Sie sollten nun eine Vorstellung davon haben, wie nett Multitexturing wirklich ist. Wir implementieren nun einen 3-Pass nicht-Multitexturing Algorithmus, welcher in einen 2-Pass Multitexuring Algorithmus entwickelt werden kann (und wird).

Nun sollten Sie etwas aufpassen, da wir ein paar Matrix-Matrix-Multiplikationen machen müssen (und Matrix-Vektor-Multikplikationen ebenfalls). Aber nichts wovor man Angst haben müsste: OpenGL wird die Matrix-Matrix-Multiplikation für uns übernehmen (wenn man es richtig anstellt) und die Matrix-Vektor-Multiplikatian ist wirklich einfach: VMatMult(M,v) multipliziert die Matrix M mit dem Vektor v und speichert das Ergebniss in v: v:=M*v. Alle Matrizen und Vektoren, die übergeben werden, müssen in homogenen-Koordinaten sein, woraus sich dann 4x4 Matrizen und 4-dim Vektoren ergeben. Damit wird die Konformität zu OpenGL sichergestellt, für den Fall, dass unsere eigenen Vektoren mit OpenGL-Matrizen multipliziert werden.

// berechne v=vM, M ist 4x4 in Spalten-Form, v ist 4dim. Zeile (z.B. "Transponiert")
void VMatMult(GLfloat *M, GLfloat *v) {
	GLfloat res[3];
	res[0]=M[ 0]*v[0]+M[ 1]*v[1]+M[ 2]*v[2]+M[ 3]*v[3];
	res[1]=M[ 4]*v[0]+M[ 5]*v[1]+M[ 6]*v[2]+M[ 7]*v[3];
	res[2]=M[ 8]*v[0]+M[ 9]*v[1]+M[10]*v[2]+M[11]*v[3];
	v[0]=res[0];
	v[1]=res[1];
	v[2]=res[2];
	v[3]=M[15];								// Homogene Koordinate
}

Anfang Theorie ( Emboss Bump Mapping Algorithmen )

Hier werden wir zwei verschiedene Algorithmen diskutieren. Ich habe den ersten erst ein paar Tage zuvor gefunden und zwar unter:
http://www.nvidia.com/marketing/Developer/DevRel.nsf/TechnicalDemosFrame?OpenPage

Das Programm heißt GL_BUMP und wurde von Diego Tártara im Jahre 1999 geschrieben.
Es implementiert wirklich gut aussehendes Bump Mapping auch wenn es einige Nachteile hat.
Aber zuerst werfen wir einen Blick auf Tártara’s Algorithmus:
  1. Alle Vektoren müssen ENTWEDER im Objekt ODER Welt Raum sein
  2. Berechne Vector v aus dem aktuellen Vertex zur Lichtposition
  3. Normalisiere v
  4. Projeziere v in den tangenten Raum. (Das ist die Ebene welche die Oberfläche im aktuellen Vertex berührt. Normalerweise ist das, wenn man mit flachen Oberflächen arbeitet, die Oberfläche selber).
  5. Verschiebe (s,t)-Koordinaten um die projizierte v’s x und y Komponente
Das sieht gar nicht schlecht aus! Es ist grundsätzlich der Algorithmus, der von oben von Michael I. Gold vorgestellt wurde. Aber er hat einen gravierenden Nachteil: Tártara macht die Projektion nur für eine xy-Ebene! Das ist für unser Vorhaben gravierend, da es den Projektions-Schritt vereinfacht, indem nur die xy-Komponenten von v genommen werden und die Z-Komponente verworfen wird.

Aber seine Implementation realsiert die diffuse Belechtung genauso, wie wir es machen: mittels der von OpenGL vordefinierten Beleuchtung. Da wir die Kombinier-Methode, die Gold vorschlägt, nicht verwenden können (wir wollen, dass unsere Programme überall laufen können, nicht nur auf TNT-Karten!), können wir den diffusen Faktor nicht im Alpha-Kanal speichern. Da wir bereits ein 3-Pass Non-Multitexturing / 2-Pass Multitextur Problem haben, warum sollten wir im letzten Durchgang nicht einfach die OpenGL-Beleuchtung anwenden und das ambiente Licht und den Farben-Kram für uns machen lassen? Das ist möglich (und sieht recht gut aus), da wir keine komplexe Geometrie verwenden, behalten Sie das im Hinterkopf. Wenn Sie mehrere tausend Bump Mapped Dreiecke rendern wollen, versuchen Sie etwas Neues zu erfinden!

Des weiteren verwendet er Multitexturing, was, wie wir sehen solten, nicht so einfach ist, wie Sie vielleicht gedacht haben, in Bezug auf diesen speziellen Fall.

Aber nun zu unserer Implementation. Sie sieht ziemlich genauso aus, wie der obige Algorithmus, außer des Projektions-Schrittes, wo wir etwas eigenes verwenden:

Warum das Gut ist: Nachteile:

Diese Abbildung zeigt, wo sich unsere Vektoren befinden. Sie erhalten t und s indem Sie einfach die adjazenten Vertices subtrahieren, aber stellen Sie vorher sicher, dass sie in die richtige Richtung zeigen und normalisieren Sie sie. Der blaue Punkt markiert den Vertex wo texCoord2f(0.0f,0.0f) hingemapped wird.

Ende Theorie ( Emboss Bump Mapping Algorithmen )

Lassen Sie uns zuerst einen Blick auf die Textur-Koordinaten Offset Erzeugung werfen. Die Funktion wird SetUpBumps() genannt, da sie genau das macht:

// initialisiere die Textur-Offsets
// n : Normalenvektor auf der Oberfläche. Muss die Länge 1 haben
// c : aktueller Vertex auf der Oberfläche
// l : Lichtposition
// s : Richtung der s-Textur-Koordinate im Objekt-Raum (muss normalisiert sein!)
// t : Richtung der t-Textur-Koordinate im Objekt-Raum (muss normalisiert sein!)
void SetUpBumps(GLfloat *n, GLfloat *c, GLfloat *l, GLfloat *s, GLfloat *t) {
	GLfloat v[3];								// Vector von der aktuellen Position zum Licht
	GLfloat lenQ;								// wird für die Normalisierung verwendet
	// berechne v aus dem aktuellen Vertex c zur Lichtposition und Normalisiere v
	v[0]=l[0]-c[0];
	v[1]=l[1]-c[1];
	v[2]=l[2]-c[2];
	lenQ=(GLfloat) sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
	v[0]/=lenQ;
	v[1]/=lenQ;
	v[2]/=lenQ;
	// Projeziere v so, dass wir zwei Werte entlängs jeder Textur-Koordinaten-Achse erhalten
	c[0]=(s[0]*v[0]+s[1]*v[1]+s[2]*v[2])*MAX_EMBOSS;
	c[1]=(t[0]*v[0]+t[1]*v[1]+t[2]*v[2])*MAX_EMBOSS;
}

Sieht nicht allzu kompliziert mehr aus, oder? Aber die Thorie ist nötig, um den Effekt zu verstehen und kontrollieren zu können. (Ich habe DAS selbst gelernt, als ich dieses Tutorial geschrieben habe).

Ich mag es immer, wenn Logos angezeigt werden, während ein Präsentationsprogramme laufen. Wir haben zur Zeit zwei davon. Da ein Aufruf von doLogo() die GL_MODELVIEW-Matrix resettet, muss dies als letzter Rendering-Durchlauf aufgerufen werden.

Diese Funktion zeigt zwei Logos an: ein OpenGL-Logo und ein Multitexture-Logo, wenn dieses Feature aktiviert ist. Die Logos sind Alpha-blended und etwas Halbtransparent. Da sie einen Alpha-Kanal haben, blende ich sie mittels GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, wie von allen OpenGL-Dokumentationen vorgeschlagen wird. Da sie alle Co-planar sind, müssen wir nicht erst nach z sortieren. Die Zahlen die für die Vertices verwendet werden sind "empirisch" (auch bekannt als try-and-error), um sie nett in den Screen-Ecken zu platzieren. Wir müssen Blending aktivieren und Beleuchtung deaktivieren, um hässliche Effekte zu vermeiden. Um sicher zu gehen, dass sie vor allem anderen sind, resetten Sie einfach die GL_MODELVIEW-Matrix und setzen die Depth-Funktion auf GL_ALWAYS.

void doLogo(void) {
	// MUSS ALS LETZTES AUFGERUFEN WERDEN!!!, Billboarded die zwei Logos
	glDepthFunc(GL_ALWAYS);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	glDisable(GL_LIGHTING);
	glLoadIdentity();
	glBindTexture(GL_TEXTURE_2D,glLogo);
	glBegin(GL_QUADS);
		glTexCoord2f(0.0f,0.0f);	glVertex3f(0.23f, -0.4f,-1.0f);
		glTexCoord2f(1.0f,0.0f);	glVertex3f(0.53f, -0.4f,-1.0f);
		glTexCoord2f(1.0f,1.0f);	glVertex3f(0.53f, -0.25f,-1.0f);
		glTexCoord2f(0.0f,1.0f);	glVertex3f(0.23f, -0.25f,-1.0f);
	glEnd();
	if (useMultitexture) {
		glBindTexture(GL_TEXTURE_2D,multiLogo);
		glBegin(GL_QUADS);
			glTexCoord2f(0.0f,0.0f);	glVertex3f(-0.53f, -0.25f,-1.0f);
			glTexCoord2f(1.0f,0.0f);	glVertex3f(-0.33f, -0.25f,-1.0f);
			glTexCoord2f(1.0f,1.0f);	glVertex3f(-0.33f, -0.15f,-1.0f);
			glTexCoord2f(0.0f,1.0f);	glVertex3f(-0.53f, -0.15f,-1.0f);
		glEnd();
	}
}

Hier kommt die Funktion um Bump Mapping ohne Multitexturing zu realisieren. Es ist eine Drei-Pass-Implementation. Als erster Schritt wird die GL_MODELVIEW Matrix invertiert, indem die Identity-Matrix angewandt wird, und alle folgenden Schritte wird die GL_MODELVIEW angewandt, in umgekehrter Reihenfolge und invertiert. Das Ergebniss ist eine Matrix, die die GL_MODELVIEW "ungeschehen" macht, wenn sie auf ein Objekt angewandt wird. Wir holen sie einfach von OpenGL, indem wir glGetFloatv() verwenden. Denken Sie daran, dass die Matrix ein Matrix mit 16 Elementen sein muss und dass die Matrix "transponiert" ist!

Übrigens: Wenn Sie nicht genau wissen, wie die Modelview erzeugt wurde, sollten Sie in Betracht ziehen, den Welten-Raum zu verwenden, da Matrix-Invertierungen kompliziert und kostspielig sind. Wenn Sie aber größere Mengen an Vertices haben, könnte das invertieren der Modelview mit einem allgemeinerem Vorhaben schneller sein.

bool doMesh1TexelUnits(void) {
	GLfloat c[4]={0.0f,0.0f,0.0f,1.0f};					// enthält den aktuellenVertex
	GLfloat n[4]={0.0f,0.0f,0.0f,1.0f};					// Normalisiere Normalenvektor der aktuellen Oberfläche
	GLfloat s[4]={0.0f,0.0f,0.0f,1.0f};					// s-Textur Koordinate Richtung, normalisiert
	GLfloat t[4]={0.0f,0.0f,0.0f,1.0f};					// t-Textur Koordinate Richtung, normalisiert
	GLfloat l[4];								// enthält unsere Lichtposition die in den Objekt-Raum transformiert werden soll
	GLfloat Minv[16];							// enthält die invertierte Modelview Matrix, um das zu machen
	int i;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// lösche den Screen und den Depth Buffer

	// erzeuge als erstes die inverse Modelview Matrix. Dies substituiert ein Push/Po mit einemglLoadIdentity();
	// erzeuge sie einfach indem alle Transformationen negiert werden und zwar in umgekehrter Reihenfolge
	glLoadIdentity();
	glRotatef(-yrot,0.0f,1.0f,0.0f);
	glRotatef(-xrot,1.0f,0.0f,0.0f);
	glTranslatef(0.0f,0.0f,-z);
	glGetFloatv(GL_MODELVIEW_MATRIX,Minv);
	glLoadIdentity();
	glTranslatef(0.0f,0.0f,z);
	glRotatef(xrot,1.0f,0.0f,0.0f);
	glRotatef(yrot,0.0f,1.0f,0.0f);

	// Transformiere die Lichtposition in Objekt-Koordinaten:
	l[0]=LightPosition[0];
	l[1]=LightPosition[1];
	l[2]=LightPosition[2];
	l[3]=1.0f;								// Homogene Koordinate
	VMatMult(Minv,l);

Erster Durchlauf: Dies wird einen Würfel zeichnen, der nur aus Bump Map besteht.

	glBindTexture(GL_TEXTURE_2D, bump[filter]);
	glDisable(GL_BLEND);
	glDisable(GL_LIGHTING);
	doCube();

Zweiter Durchlauf: Dies wird den Würfel mit korrektem Emboss-Bump-Mapping rendern, aber ohne Farben.

Sie könnten Rechenzeit sparen, indem Sie den Lichtvektor einfach in die invertierte Richtung rotieren. Allerdings hat das nicht richtig funktioniert, weshalb wir den einfachen Weg gehen: jeden Normalenvektor und Mittelpunkt rotieren lassen, genauso wie wir unsere Geometrie rotieren lassen!

	glBindTexture(GL_TEXTURE_2D,invbump[filter]);
	glBlendFunc(GL_ONE,GL_ONE);
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_BLEND);

	glBegin(GL_QUADS);
		// vordere Seite
		n[0]=0.0f;
		n[1]=0.0f;
		n[2]=1.0f;
		s[0]=1.0f;
		s[1]=0.0f;
		s[2]=0.0f;
		t[0]=0.0f;
		t[1]=1.0f;
		t[2]=0.0f;
		for (i=0; i<4; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// hintere Seite
		n[0]=0.0f;
		n[1]=0.0f;
		n[2]=-1.0f;
		s[0]=-1.0f;
		s[1]=0.0f;
		s[2]=0.0f;
		t[0]=0.0f;
		t[1]=1.0f;
		t[2]=0.0f;
		for (i=4; i<8; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// obere Seite
		n[0]=0.0f;
		n[1]=1.0f;
		n[2]=0.0f;
		s[0]=1.0f;
		s[1]=0.0f;
		s[2]=0.0f;
		t[0]=0.0f;
		t[1]=0.0f;
		t[2]=-1.0f;
		for (i=8; i<12; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// untere Seite
		n[0]=0.0f;
		n[1]=-1.0f;
		n[2]=0.0f;
		s[0]=-1.0f;
		s[1]=0.0f;
		s[2]=0.0f;
		t[0]=0.0f;
		t[1]=0.0f;
		t[2]=-1.0f;
		for (i=12; i<16; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// rechte Seite
		n[0]=1.0f;
		n[1]=0.0f;
		n[2]=0.0f;
		s[0]=0.0f;
		s[1]=0.0f;
		s[2]=-1.0f;
		t[0]=0.0f;
		t[1]=1.0f;
		t[2]=0.0f;
		for (i=16; i<20; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// linke Seite
		n[0]=-1.0f;
		n[1]=0.0f;
		n[2]=0.0f;
		s[0]=0.0f;
		s[1]=0.0f;
		s[2]=1.0f;
		t[0]=0.0f;
		t[1]=1.0f;
		t[2]=0.0f;
		for (i=20; i<24; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glTexCoord2f(data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
	glEnd();

Dritter Durchlauf: Damit wird das Würfel-rendern beendet, komplett mit Beleuchtung. Da wir zwischen Multitexturing und Non-Multitexturing hin- und herspringen können, müssen wir die Textur-Umgebung als erstes auf "normales" GL_MODULATE resetten. Wir machen den dritten Durchlauf nur dann, wenn der Benutzer nicht nur den Emboss sehen will.

	if (!emboss) {
		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glBindTexture(GL_TEXTURE_2D,texture[filter]);
		glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
		glEnable(GL_LIGHTING);
		doCube();
	}

Letzter Durchlauf:

	xrot+=xspeed;
	yrot+=yspeed;
	if (xrot>360.0f) xrot-=360.0f;
	if (xrot<0.0f) xrot+=360.0f;
	if (yrot>360.0f) yrot-=360.0f;
	if (yrot<0.0f) yrot+=360.0f;

	/* LETZTER DURCHLAUF: Mache die Logos! */
	doLogo();
	return true;								// weiter gehts
}

Diese Funktion wird den ganzen Kram in 2 Durchläufen inklusive Multitexturing Unterstützung machen. Wir unterstützen zwei Texel-Einheiten. Mehr währen extrem kompliziert, auf Grund der Blending-Gleichung. Dann sollte man besser zu TNT wechseln. Beachten Sie, dass fast der einzige Unterschied zu doMesh1TexelUnits() lediglich der ist, dass wir zwei Sätze an Textur-Koordinaten für jeden Vertex senden!

bool doMesh2TexelUnits(void) {
	GLfloat c[4]={0.0f,0.0f,0.0f,1.0f};					// enthält aktuellen Vertex
	GLfloat n[4]={0.0f,0.0f,0.0f,1.0f};					// Normalisierter Normalenvektor der aktuellen Oberfläche
	GLfloat s[4]={0.0f,0.0f,0.0f,1.0f};					// s-Textur Koordinate Richtung, normalisiert
	GLfloat t[4]={0.0f,0.0f,0.0f,1.0f};					// t-Textur Koordinate Richtung, normalisiert
	GLfloat l[4];								// enthält unsere Lichtposition die in den Objekt-Raum transformiert werden soll
	GLfloat Minv[16];							// enthält die invertierte Modelview Matrix, um das zu machen
	int i;

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// lösche den Screen und den Depth Buffer

	// erzeuge als erstes die inverse Modelview Matrix. Dies substituiert ein Push/Po mit einemglLoadIdentity();
	// erzeuge sie einfach indem alle Transformationen negiert werden und zwar in umgekehrter Reihenfolge
	glLoadIdentity();
	glRotatef(-yrot,0.0f,1.0f,0.0f);
	glRotatef(-xrot,1.0f,0.0f,0.0f);
	glTranslatef(0.0f,0.0f,-z);
	glGetFloatv(GL_MODELVIEW_MATRIX,Minv);
	glLoadIdentity();
	glTranslatef(0.0f,0.0f,z);

	glRotatef(xrot,1.0f,0.0f,0.0f);
	glRotatef(yrot,0.0f,1.0f,0.0f);

	// Transformiere die Lichtposition in Objekt-Koordinaten:
	l[0]=LightPosition[0];
	l[1]=LightPosition[1];
	l[2]=LightPosition[2];
	l[3]=1.0f;								// Homogene Koordinate
	VMatMult(Minv,l);

Erster Durchlauf: Initialisiere die Textur-Einheit 0 mit Initialisiere die Textur-Einheit 1 mit Dies rendert den Würfel, bestehend aus eine grauskalierten Map.

	// TEXTUR-EINHEIT #0
	glActiveTextureARB(GL_TEXTURE0_ARB);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, bump[filter]);
	glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
	glTexEnvf (GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE);

	// TEXTUR-EINHEIT #1
	glActiveTextureARB(GL_TEXTURE1_ARB);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, invbump[filter]);
	glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
	glTexEnvf (GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_ADD);

	// generelle Schalter
	glDisable(GL_BLEND);
	glDisable(GL_LIGHTING);

Nun rendere eine Seite nach der anderen, genauso wie es bereits in doMesh1TexelUnits() gemacht wurde. Einzige Neuigkeit: es wird glMultiTexCoor2fARB() statt nur glTexCoord2f() verwendet. Beachten Sie, dass Sie angeben müssen, welche Textur-Einheit Sie mit dem ersten Parameter meinen, welcher gleich GL_TEXTUREi_ARB sein muss, wobei i eine Zahl aus [0..31] sein kann. (Welche Hardware hat 32 Textur-Einheiten? Und wofür?)

	glBegin(GL_QUADS);
		// vordere Seite
		n[0]=0.0f;
		n[1]=0.0f;
		n[2]=1.0f;
		s[0]=1.0f;
		s[1]=0.0f;
		s[2]=0.0f;
		t[0]=0.0f;
		t[1]=1.0f;
		t[2]=0.0f;
		for (i=0; i<4; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);
			glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// hintere Seite
		n[0]=0.0f;
		n[1]=0.0f;
		n[2]=-1.0f;
		s[0]=-1.0f;
		s[1]=0.0f;
		s[2]=0.0f;
		t[0]=0.0f;
		t[1]=1.0f;
		t[2]=0.0f;
		for (i=4; i<8; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);
			glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// obere Seite
		n[0]=0.0f;
		n[1]=1.0f;
		n[2]=0.0f;
		s[0]=1.0f;
		s[1]=0.0f;
		s[2]=0.0f;
		t[0]=0.0f;
		t[1]=0.0f;
		t[2]=-1.0f;
		for (i=8; i<12; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);
			glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// untere Seite
		n[0]=0.0f;
		n[1]=-1.0f;
		n[2]=0.0f;
		s[0]=-1.0f;
		s[1]=0.0f;
		s[2]=0.0f;
		t[0]=0.0f;
		t[1]=0.0f;
		t[2]=-1.0f;
		for (i=12; i<16; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);
			glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// rechte Seite
		n[0]=1.0f;
		n[1]=0.0f;
		n[2]=0.0f;
		s[0]=0.0f;
		s[1]=0.0f;
		s[2]=-1.0f;
		t[0]=0.0f;
		t[1]=1.0f;
		t[2]=0.0f;
		for (i=16; i<20; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);
			glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
		// Linke Seite
		n[0]=-1.0f;
		n[1]=0.0f;
		n[2]=0.0f;
		s[0]=0.0f;
		s[1]=0.0f;
		s[2]=1.0f;
		t[0]=0.0f;
		t[1]=1.0f;
		t[2]=0.0f;
		for (i=20; i<24; i++) {
			c[0]=data[5*i+2];
			c[1]=data[5*i+3];
			c[2]=data[5*i+4];
			SetUpBumps(n,c,l,s,t);
			glMultiTexCoord2fARB(GL_TEXTURE0_ARB,data[5*i], data[5*i+1]);
			glMultiTexCoord2fARB(GL_TEXTURE1_ARB,data[5*i]+c[0], data[5*i+1]+c[1]);
			glVertex3f(data[5*i+2], data[5*i+3], data[5*i+4]);
		}
	glEnd();

Zweiter Durchlauf Dies wir unseren kompletten bump-mapped Würfel rendern.

	glActiveTextureARB(GL_TEXTURE1_ARB);
	glDisable(GL_TEXTURE_2D);
	glActiveTextureARB(GL_TEXTURE0_ARB);
	if (!emboss) {
		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glBindTexture(GL_TEXTURE_2D,texture[filter]);
		glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
		glEnable(GL_BLEND);
		glEnable(GL_LIGHTING);
		doCube();
	}

Letzter Durchlauf

	xrot+=xspeed;
	yrot+=yspeed;
	if (xrot>360.0f) xrot-=360.0f;
	if (xrot<0.0f) xrot+=360.0f;
	if (yrot>360.0f) yrot-=360.0f;
	if (yrot<0.0f) yrot+=360.0f;

	/* LETZTER DURCHLAUF: bearbeite die Logos! */
	doLogo();
	return true;								// weiter gehts
}

Zu guter Letzt eine Funktion, um einen Würfel ohne Bumpmapping zu rendern, damit Sie den Unterschied sehen können!

bool doMeshNoBumps(void) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// lösche den Screen und den Depth Buffer
	glLoadIdentity();							// Resette die View
	glTranslatef(0.0f,0.0f,z);

	glRotatef(xrot,1.0f,0.0f,0.0f);
	glRotatef(yrot,0.0f,1.0f,0.0f);

	if (useMultitexture) {
		glActiveTextureARB(GL_TEXTURE1_ARB);
		glDisable(GL_TEXTURE_2D);
		glActiveTextureARB(GL_TEXTURE0_ARB);
	}

	glDisable(GL_BLEND);
	glBindTexture(GL_TEXTURE_2D,texture[filter]);
	glBlendFunc(GL_DST_COLOR,GL_SRC_COLOR);
	glEnable(GL_LIGHTING);
	doCube();

	xrot+=xspeed;
	yrot+=yspeed;
	if (xrot>360.0f) xrot-=360.0f;
	if (xrot<0.0f) xrot+=360.0f;
	if (yrot>360.0f) yrot-=360.0f;
	if (yrot<0.0f) yrot+=360.0f;

	/* LETZTER DURCHLAUF: bearbeite die Logos! */
	doLogo();
	return true;								// weiter gehts
}